from linear_regress_functions import *

# 生成数据集 features训练数据特征，labels是标签
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = torch.tensor(np.random.normal(0, 1, (num_examples, num_inputs)), dtype=torch.float32)
labels = features[:, 0]*true_w[0] + features[:, 1]*true_w[1]+true_b
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float32)

# 读取数据

batch_size = 10
# 将训练数据的特征和标签组合
dataset = Data.TensorDataset(features, labels)
# 随机读取小批量
data_iter = Data.DataLoader(dataset, batch_size, shuffle=True)
for x,y in data_iter:
    print(x,y)
    break

# net = LinearNet(num_inputs)
# print(net)

net = nn.Sequential(OrderedDict([
 ('linear', nn.Linear(num_inputs, 1))
 # ......
 ]))
print(net)
print(net[0])
print("net[0]层刚生成时的参数net[0].weight:", net[0].weight)
print("net[0]层刚生成时的参数net[0].bias:", net[0].bias)
# 可以通过 net.parameters() 来查看模型所有的可学习参数，此函数将返回⼀个⽣成器。, net.parameters()中是net[0]的初始参数net[0].weight, net[0].bias
for param in net.parameters():
    print(param)
"""
nn.Sequential一个有序的容器，神经网络模块将按照在传入构造器的顺序依次被添加到计算图中执行，同时以神经网络模块为元素的有序字典也可以作为传入参数。
"""
# 最简单的序贯模型
Net = nn.Sequential(
                  nn.Conv2d(1, 20, 5),
                  nn.ReLU(),
                  nn.Conv2d(20, 64, 5),
                  nn.ReLU()
                )
print(Net)
print(Net[2])
# 给每个层添加名字
model = nn.Sequential(OrderedDict([
    ('conv1', nn.Conv2d(1, 20, 5)),
    ('relu1', nn.ReLU()),
    ('conv2', nn.Conv2d(20, 64, 5)),
    ('relu2', nn.ReLU())
]))

print(model)
print(model[2])

# 初始化模型参数
nn.init.normal_(net[0].weight, mean=0, std=0.01)
nn.init.constant_(net[0].bias, val=0)
print("net[0]层初始化后的参数net[0].weight:", net[0].weight)
print("net[0]层初始化后的参数net[0].bias:", net[0].bias)

# 定义损失函数
# PyTorch在 nn 模块中提供了各种损失函数，这些损失函数可看作是⼀种特殊的层，PyTorch也将这些损失函数实现为 nn.Module 的⼦类。我们现在使⽤它提供的均⽅误差损失作为模型的损失函数
loss=nn.MSELoss()

# 定义优化算法
# torch.optim 模块提供了很多常⽤的优化算法⽐如SGD、Adam和RMSProp等。下⾯我们创建⼀个⽤于优化 net 所有参数的优化器实例，并指定学习率为0.03的⼩批量随机梯度下降（SGD）为优化算法。
optimizer = optim.SGD(net.parameters(), lr=0.03)
print(optimizer)
"""
我们还可以为不同⼦⽹络设置不同的学习率，这在finetune时经常⽤到。例：
optimizer =optim.SGD([
 # 如果对某个参数不指定学习率，就使⽤最外层的默认学习率
 {'params': net.subnet1.parameters()}, # lr=0.03
 {'params': net.subnet2.parameters(), 'lr': 0.01}
 ], lr=0.03)
"""
# 有时候我们不想让学习率固定成⼀个常数，那如何调整学习率呢？主要有两种做法。⼀种是修改 optimizer.param_groups 中对应的学习率，另⼀种是更简单也是较为推荐的做法——新建优化器
for param_group in optimizer.param_groups:
    param_group['lr'] *= 0.1

# 训练模型
num_epochs = 5
for epoch in range(num_epochs):
    for X, y in data_iter:
        output = net(X)
        l = loss(output, y.view(-1, 1))
        optimizer.zero_grad()
        l.backward()
        """
        l.backward()
        RuntimeError: grad can be implicitly created only for scalar outputs
        l为张量，必须转换成标量
        """
        optimizer.step()
    print("epoch %d, loss: %f", (epoch, l.item()))

dense = net[0]
print(true_w, dense.weight)
print(true_b, dense.bias)